/*
 * 巴法科技:2022-11-22
 * 支持巴法app和小程序配网，长按按键可重新配网
 */
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ArduinoJson.h>
#include <ESP8266httpUpdate.h>

WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;

#define TCP_SERVER_ADDR "bemfa.com" // 巴法云服务器地址默认即可
#define TCP_SERVER_PORT "8344"      // 服务器端口，tcp创客云端口8344
//*****可以修改的地方******//
String aptype = "004";  // 001插座类型，002灯类型，003风扇类型，004传感器，005空调，006开关，009窗帘
String Name = "充电器"; // 设备昵称，可随意修改
String verSion = "3.1"; // 3.1是tcp协议,1.1是mqtt协议,
String room = "";       // 房间。例如客厅、卧室等，默认空
int adminID = 0;        // 默认空即可。企业id,建议企业用户配置，该设备会自动绑定到该企业下，获取id方法见接入文档5.17节
int protoType = 3;      // 3是tcp设备端口8344,1是MQTT设备
int failCount = 0;      // 定义失败连接次数

const int U_pwm = 4;     // 限压引脚
const int I_pwm = 5;     // 限流引脚
const int greenLED = 13; // 蓝色指示灯引脚，
const int redLED = 12;   // 红色指示灯引脚，
const int sdin = 0;      // 电源连接状态引脚
const int xdcin = 16;    // 电池连接状态引脚
const int out = 15;      // 充电器输出引脚
const int fen = 14;      // 风扇引脚
const int bh = 2;        // 保护引脚
const int charts = 10;   // 充电记录数

// 检测是否是第一次连接WIFI
bool firstWIfiConfig = false;
String topic = "";

// 最大字节数
#define MAX_PACKETSIZE 512
// 设置心跳值30s
unsigned long KEEPALIVEATIME = 2 * 1000;

// tcp客户端相关初始化，默认即可
WiFiClient TCPclient;
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;    // 心跳
unsigned long preTCPStartTick = 0; // 连接
bool fasong = false;

int httpCode = 0;
String UID = "";
String TOPIC = "";
#define HOST_NAME "bemfa"
char config_flag = 0;
#define MAGIC_NUMBER 0xAA

// 传感器参数
float Ug; // 工作电压
float u;  // 读取电压
float U;  // 读取电压
float I;  // 读取电流
float P;  // 读取功率
float Ah; // 读取单次电能
int Wh;   // 读取合计电能
int t;    // 读取温度
int Q;    // 充电百分比
int f5;   // 程序刷新率

// 相关函数初始化
void doWiFiTick();
void doTCPClientTick();
void startTCPClient();
void sendtoTCPServer();
void sendtoTCPServer(String p);
void startSTA(String stassid, String stapsw);

/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  char stassid[32];
  char stapsw[64];
  char cuid[40];
  char ctopic[32];
  uint8_t reboot;
  uint8_t magic;
  uint8_t Ud;                 // 待机电压
  uint8_t Um;                 // 充电电压
  uint8_t I;                  // 充电电流
  uint8_t If;                 // 均衡电流
  float mu;                   // 进入满电电压
  float fu;                   // 进入浮充电压
  float Ah;                   // 蓄电池容量
  float bi;                   // 读取开机时的电能
  float shu;                  // 已有数据数组
  float charts[10];           // 充电电能数组
  float bt152_600;            // 可控硅压降
  float equivalentResistance; //  线损电阻
};
config_type config;
uint8_t *p = (uint8_t *)(&config);
char packetBuffer[255]; // 发送数据包
WiFiUDP Udp;

void waitKey();
void saveConfig();
void initWiFi();
void loadConfig();
void restoreData();
void restoreFactory();

#include "Charger.h"

// 当升级开始时，打印日志
void update_started()
{
  Serial.println("CALLBACK:  HTTP update process started");
}

// 当升级结束时，打印日志
void update_finished()
{
  Serial.println("CALLBACK:  HTTP update process finished");
  digitalWrite(redLED, LOW);   // 切换红色指示灯状态
  digitalWrite(greenLED, LOW); // 切换红色指示灯状态
}

// 当升级中，打印日志
void update_progress(int cur, int total)
{
  if (digitalRead(redLED) != digitalRead(greenLED))
  {
    digitalWrite(redLED, !digitalRead(redLED)); // 切换红色指示灯状态
  }
  digitalWrite(redLED, !digitalRead(redLED));     // 切换红色指示灯状态
  digitalWrite(greenLED, !digitalRead(greenLED)); // 切换红色指示灯状态
}

// 当升级失败时，打印日志
void update_error(int err)
{
  Serial.printf("CALLBACK:  HTTP update fatal error code %d\n", err);
}

/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin();
 * 原理：通过http请求获取远程固件，实现升级
 */
void updateBin()
{
  String upUrl = "http://bin.bemfa.com/b/3BcNDg5MmJkN2ZlMDA1ZWNiYmNjZjM1OTI5MTU3ZWM3ZTc=" + TOPIC + ".bin"; // OTA固件链接，请替换为自己的固件链接，如果接收到msg=update，开始执行固件升级
  Serial.println("");
  Serial.println(upUrl);
  WiFiClient UpdateClient;
  ESPhttpUpdate.onStart(update_started);     // 当升级开始时
  ESPhttpUpdate.onEnd(update_finished);      // 当升级结束时
  ESPhttpUpdate.onProgress(update_progress); // 当升级中
  ESPhttpUpdate.onError(update_error);       // 当升级失败时

  t_httpUpdate_return ret = ESPhttpUpdate.update(UpdateClient, upUrl);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED: // 当升级失败
    Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES: // 当无升级
    Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK: // 当升级成功
    Serial.println("[update] Update ok.");
    break;
  }
}
uint16_t calculateChecksum(const config_type *data)
{
  uint16 sum = 0;
  const uint8_t *ptr = (const uint8_t *)data;
  size_t size = sizeof(config_type);
  for (size_t i = 0; i < size; i++)
  {
    sum += ptr[i];
  }
  return sum;
}
/**
 * 存储配网信息
 */
void saveConfig()
{
  EEPROM.begin(512);
  uint16_t chekksum = calculateChecksum(&config);
  EEPROM.put(sizeof(config_type), chekksum);
  EEPROM.put(0, config);
  EEPROM.commit();
}
/**
 * 加载存储的信息，并检查是否进行了反复5次重启恢复出厂信息
 */
void loadConfig()
{
  uint16_t readData;
  EEPROM.begin(512);
  EEPROM.get(0, config);
  EEPROM.get(sizeof(config_type), readData);
  uint16_t chekksum = calculateChecksum(&config);
  if (readData != chekksum)
  {
    Serial.println("数据异常");
    restoreData();
    restoreFactory();
  }
  else
  {
    if (config.magic != 0xAA)
    {
      config_flag = 1;
    }
    config.reboot = 0;
  }
}
// 重启函数
Ticker delayTimer;
void delayRestart(float t)
{
  delayTimer.attach(t, []()
                    { ESP.restart(); });
}

/**
 * 恢复出厂设置，清除存储的wifi信息
 */
void restoreFactory()
{
  Serial.println("Restore Factory....... ");
  config.magic = 0x00;
  strcpy(config.stassid, "");
  strcpy(config.stapsw, "");
  // strcpy(config.cuid, "");
  // strcpy(config.ctopic, "");
  config.reboot = 0;
  saveConfig();
  delayRestart(1);
  while (1)
  {
    delay(100);
  }
}
void restoreData()
{
  for (size_t i = 0; i < charts; i++)
  {
    config.charts[i] = 0;
  }
  config.Um = 243;
  config.Ud = 150;
  config.I = 200;
  config.If = 1;
  config.mu = 84;
  config.fu = 82;
  config.Ah = 20;
  config.bi = Wh;
  config.shu = 0;
  config.bt152_600 = 0.72;
  config.equivalentResistance = 0.01;
  saveConfig();
}

/*
  第一次配网检查WIFI,保存WIFI配置信息,并创建主题
*/
void checkFirstConfig()
{
  if (firstWIfiConfig)
  {
    // 设置目标 URL
    http_bemfa_HTTPClient.begin(client_bemfa_WiFiClient, "http://pro.bemfa.com/vs/web/v1/deviceAddTopic");
    // 创建 JSON 对象
    StaticJsonDocument<200> jsonDoc;
    jsonDoc["uid"] = config.cuid;
    jsonDoc["name"] = Name;
    jsonDoc["topic"] = topic;
    jsonDoc["type"] = protoType;
    jsonDoc["room"] = room;
    jsonDoc["adminID"] = adminID;
    jsonDoc["wifiConfig"] = 1; // 必填字段

    // 将 JSON 对象转换为字符串
    String jsonString;
    serializeJson(jsonDoc, jsonString);
    http_bemfa_HTTPClient.addHeader("Content-Type", "application/json; charset=UTF-8");
    // 发送请求
    int httpCode = http_bemfa_HTTPClient.POST(jsonString);
    if (httpCode == 200)
    {
      Serial.println("POST succeeded with code:");
      Serial.println(httpCode);
      String payload = http_bemfa_HTTPClient.getString();
      Serial.println(payload);

      // json数据解析
      StaticJsonDocument<200> doc;
      DeserializationError error = deserializeJson(doc, payload);
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.c_str());
      }

      int code = doc["code"];
      if (code == 0)
      {
        int resCode = doc["data"]["code"];
        if (resCode == 40006 || resCode == 0)
        {
          String docUID = doc["uid"];
          Serial.print("create topic ok:");
          Serial.println(topic);
          if (firstWIfiConfig)
          {
            config.reboot = 0;
            config.magic = 0xAA;
            saveConfig();
          }
        }
        else
        {
          Serial.println(" config ERROR.........");
        }
      }
      else
      {
        Serial.println(" config ERROR.........");
      }
    }
    else if (httpCode != 200)
    {
      Serial.println("POST failed with code:");
      Serial.println(httpCode);
    }
    else
    {
      Serial.println("Unknown error");
    }
    http_bemfa_HTTPClient.end();
  }
}

void apConfig()
{
  topic = WiFi.macAddress().substring(8); // 取mac地址做主题用
  topic.replace(":", "");                 // 去掉:号
  topic = topic + aptype;
  WiFi.softAP("bemfa_" + topic);
  Udp.begin(8266);
  Serial.println("Started Ap Config...");

  while (config_flag)
  { // 如果未配网，开启AP配网，并接收配网信息
    int packetSize = Udp.parsePacket();
    if (packetSize)
    {
      Serial.print("Received packet of size ");
      Serial.println(packetSize);
      Serial.print("From ");
      IPAddress remoteIp = Udp.remoteIP();
      Serial.print(remoteIp);
      Serial.print(", port ");
      Serial.println(Udp.remotePort());

      int len = Udp.read(packetBuffer, 255);
      if (len > 0)
      {
        packetBuffer[len] = 0;
      }
      Serial.println("Contents:");
      Serial.println(packetBuffer);
      StaticJsonDocument<200> doc;

      DeserializationError error = deserializeJson(doc, packetBuffer);
      if (error)
      {
        Serial.print(F("deserializeJson() failed: "));
        Serial.println(error.f_str());
        return;
      }
      int cmdType = doc["cmdType"].as<int>();
      ;

      if (cmdType == 1)
      {
        const char *ssid = doc["ssid"];
        const char *password = doc["password"];
        const char *token = doc["token"];
        strcpy(config.stassid, ssid);
        strcpy(config.stapsw, password);
        strcpy(config.cuid, token);
        strcpy(config.ctopic, topic.c_str());

        // 收到信息，并回复
        String ReplyBuffer = "{\"cmdType\":2,\"productId\":\"" + topic + "\",\"deviceName\":\"" + Name + "\",\"protoVersion\":\"" + verSion + "\"}";
        Udp.beginPacket(Udp.remoteIP(), Udp.remotePort());
        Udp.write(ReplyBuffer.c_str());
        Udp.endPacket();
      }
      else if (cmdType == 3)
      {
        config_flag = 0;
        firstWIfiConfig = true;
        WiFi.softAPdisconnect(true);
        UID = config.cuid;
        TOPIC = config.ctopic;
        initWiFi();
      }
    }
    digitalWrite(greenLED, !digitalRead(greenLED)); // 切换指示灯状态
    delay(100);
    if (millis() > 30000)
    {
      WiFi.softAPdisconnect(true);
      return;
    }
  }
}

/**
 * 检查是否需要airkiss配网
 */
void waitKey()
{
  if (config_flag == 1)
  {
    apConfig(); // 加载ap
  }
}

void startSTA(String stassid, String stapsw)
{

  if (stassid != "" && stapsw != "")
  {
    WiFi.disconnect();           // 断开连接
    WiFi.mode(WIFI_STA);         // STA模式
    WiFi.begin(stassid, stapsw); // 连接路由器
    Serial.println("");
    Serial.print("stassid: ");
    Serial.print(stassid);
    Serial.print("\nstapsw: ");
    Serial.print(stapsw);
    Serial.println("\nwifi config...");
  }
}
/**
 * 初始化wifi信息，并连接路由器网络
 */
void initWiFi()
{
  int num = 0;

  startSTA(config.stassid, config.stapsw);
  while (WiFi.status() != WL_CONNECTED && num < 120)
  { // 检查是否连接成功
    delay(500);
    num = num + 1;
    Serial.print(".");
    digitalWrite(greenLED, !digitalRead(greenLED)); // 切换指示灯状态
  }
  Serial.println("");
  Serial.println("wifi config ok");
  digitalWrite(greenLED, HIGH); // 切换指示灯状态
}
/*
  WiFiTick
  检查是否需要初始化WiFi
  检查WiFi是否连接上，若连接成功启动TCP Client
  控制指示灯
*/
void doWiFiTick()
{
  static bool startSTAFlag = false;
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick0 = 0;
  static uint32_t lastWiFiCheckTick1 = 0;
  // 未连接1s重连
  if (WiFi.status() != WL_CONNECTED)
  {
    if (millis() - lastWiFiCheckTick0 > 10000)
    {
      if (!startSTAFlag)
      {
        startSTAFlag = true;
        UID = config.cuid;
        TOPIC = config.ctopic;
        startSTA(config.stassid, config.stapsw);
        lastWiFiCheckTick0 = millis();
      }
      else
      {
        startSTAFlag = false;
        UID = "4892bd7fe005ecbbccf35929157ec7e7";
        TOPIC = "0796AA004";
        startSTA("Sensen", "15367660"); // 连接路由器
        // startSTA("ChinaNet-a37DHH", "88888888"); // 连接路由器

        lastWiFiCheckTick0 = millis();
      }
    }
    if (!isCharging && millis() - lastWiFiCheckTick1 > ledTime)
    {
      digitalWrite(greenLED, !digitalRead(greenLED)); // 切换指示灯状态
      lastWiFiCheckTick1 = millis();
    }
    taskStarted = false;
  }
  // 连接成功建立
  else
  {
    if (taskStarted == false)
    {
      digitalWrite(redLED, LOW);    // 切换指示灯状态
      digitalWrite(greenLED, HIGH); // 切换指示灯状态
      taskStarted = true;
      Serial.println("");
      Serial.println("wifi config ok");
      Serial.print("ip: ");
      Serial.println(WiFi.localIP());
      startTCPClient();
    }
  }
}
/*
 *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{
  if (TCPclient.connected())
  {
    TCPclient.print(p);
  }
  preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
}

void sendtoTCPServer()
{
  sendtoTCPServer("cmd=2&uid=" + UID +
                  "&topic=" + TOPIC +
                  "/up&msg=" + (digitalRead(out) ? "on" : "off") +
                  "#" + U +
                  "#" + I +
                  "#" + P +
                  "#" + Ah +
                  "#" + Wh / 1000.00 +
                  "#" + t +
                  "#" + config.charts[0] +
                  "#" + config.charts[1] +
                  "#" + config.charts[2] +
                  "#" + config.charts[3] +
                  "#" + config.charts[4] +
                  "#" + config.charts[5] +
                  "#" + config.charts[6] +
                  "#" + config.charts[7] +
                  "#" + config.charts[8] +
                  "#" + config.charts[9] +
                  "#" + chargingStatus +
                  "#" + timeInfo +
                  "#" + int(config.shu) + "\r\n"); // 推送消息
}

/*
 *初始化和服务器建立连接
 */
void startTCPClient()
{
  String tcpTemp = "cmd=1&uid=" + UID + "&topic=" + TOPIC + "\r\n"; // 构建订阅指令
  Serial.println("");
  Serial.print("发送订阅指令:");
  Serial.println(tcpTemp);

  if (TCPclient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {

    sendtoTCPServer(tcpTemp); // 发送订阅指令
    tcpTemp = "";             // 清空
    fasong = true;
    TCPclient.setNoDelay(true);
    failCount = 0;
  }
  else
  {
    // failCount = failCount + 1;
    // if (failCount > 10)
    // {
    //   WiFi.disconnect(); // 断开连接
    // }
    Serial.print("Failed connected to server:");
    Serial.println(TCP_SERVER_ADDR);
    preTCPStartTick = millis();
  }
}

// 函数用于根据传入的实际电压和电流进行校准并计算系数
float calibrateVoltage(float actualVoltage, float systemVoltage, float current)
{
  if (actualVoltage == 0)
  {
    return 0;
  }
  else
  {
    if (current != 0)
    {
      // 计算压差
      float voltageDifference = systemVoltage - actualVoltage;
      // 根据欧姆定律计算等效电阻（假设与压差相关）
      return voltageDifference / current;
    }
  }
  return 0;
}

/*
 *检查数据，发送心跳
 */
void doTCPClientTick()
{
  // 检查是否断开，断开后重连
  if (WiFi.status() != WL_CONNECTED)
  {
    return;
  }
  if (!TCPclient.connected())
  {
    if (millis() - preTCPStartTick > 5 * 1000) // 重新连接
    {
      TCPclient.stopAll();
      startTCPClient();
    }
  }
  else
  {
    if (TCPclient.available())
    { // 收数据
      char c = TCPclient.read();
      TcpClient_Buff += c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();

      if (TcpClient_BuffIndex >= MAX_PACKETSIZE - 1)
      {
        TcpClient_BuffIndex = MAX_PACKETSIZE - 2;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
    }
    if (millis() - preHeartTick >= KEEPALIVEATIME)
    { // 保持心跳
      sendtoTCPServer("cmd=0&msg=keep\r\n");
      if (fasong)
      {
        fasong = false;
        sendtoTCPServer();
      }
    }
    if ((TcpClient_Buff.length() >= 1) && (millis() - TcpClient_preTick >= 10))
    {
      TCPclient.flush();
      int a = TcpClient_Buff.indexOf("msg=") + 4;
      int b = TcpClient_Buff.indexOf("=", a);
      int c = TcpClient_Buff.indexOf("\n", a);
      if (TcpClient_Buff.length() >= 60)
      {
        if (TcpClient_Buff.indexOf("update") >= 0)
        {
          updateBin();
        }
        else if (TcpClient_Buff.indexOf("canshu") >= 0 || (a < b && b < c))
        {
          if (a < b && b < c)
          {
            String d = TcpClient_Buff.substring(a, b);
            float e = atof(TcpClient_Buff.substring(b + 1, c).c_str());
            Serial.println("" + d + "=" + e + "");
            if (d.indexOf("um") >= 0)
            {
              config.Um = e;
              analogWrite(U_pwm, e);
            }
            else if (d.indexOf("ud") >= 0)
            {
              config.Ud = e;
              analogWrite(U_pwm, e);
            }
            else if (d.indexOf("i") >= 0)
            {
              config.I = e;
              analogWrite(I_pwm, e);
            }
            else if (d.indexOf("uf") >= 0)
            {
              config.If = e;
              analogWrite(I_pwm, e);
            }
            else if (d.indexOf("ah") >= 0)
            {
              config.Ah = e;
            }
            else if (d.indexOf("mu") >= 0)
            {
              config.mu = e;
            }
            else if (d.indexOf("fu") >= 0)
            {
              config.fu = e;
            }
            else if (d.indexOf("au") >= 0)
            {
              config.equivalentResistance = calibrateVoltage(e, u, I);
            }
            else if (d.indexOf("bt") >= 0)
            {
              config.bt152_600 = e;
            }
            saveConfig();
          }
          sendtoTCPServer("cmd=2&uid=" + UID +
                          "&topic=" + TOPIC +
                          "/up&msg=canshu#" + config.Um +
                          "#" + config.If +
                          "#" + config.Ud +
                          "#" + config.I +
                          "#" + config.Ah +
                          "#" + config.mu +
                          "#" + config.fu +
                          "#" + config.bt152_600 +
                          "#" + config.equivalentResistance +
                          "#\r\n"); // 推送消息
        }
        else if (TcpClient_Buff.indexOf("out") >= 0)
        {
          digitalWrite(out, !digitalRead(out)); // 切换充电器输出状态
        }
        else if (TcpClient_Buff.indexOf("fen") >= 0)
        {
          analogWrite(fen, 1024);
        }
        else if (TcpClient_Buff.indexOf("bh") >= 0)
        {
          digitalWrite(bh, !digitalRead(bh)); // 切换保护状态
        }
        else if (TcpClient_Buff.indexOf("Resetdata") >= 0)
        {
          restoreData();
        }
        else if (TcpClient_Buff.indexOf("Reset") >= 0)
        {
          restoreFactory();
        }
        else if (TcpClient_Buff.indexOf("Rebootcd") >= 0)
        {
          isCharging = false;
          chargingStatus = "";
        }
        else if (TcpClient_Buff.indexOf("Reboot") >= 0)
        {
          delayRestart(1);
        }

        fasong = true;
        preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
      }
      TcpClient_Buff = "";
      TcpClient_BuffIndex = 0;
    }
  }
}
// 初始化，相当于main 函数
void setup()
{
  Serial.begin(9600);
  Serial.print("\n");
  Serial.println("初始化...");
  analogWriteFreq(10000);
  pinMode(sdin, INPUT);  // 引脚为输入引脚
  pinMode(xdcin, INPUT); // 引脚为输入引脚

  pinMode(bh, OUTPUT);       // 引脚为输出引脚
  pinMode(fen, OUTPUT);      // 引脚为输出引脚
  pinMode(out, OUTPUT);      // 引脚为输出引脚
  pinMode(greenLED, OUTPUT); // 引脚为输出引脚
  pinMode(redLED, OUTPUT);   // 引脚为输出引脚

  digitalWrite(bh, HIGH);
  digitalWrite(fen, HIGH);

  loadConfig();

  waitKey();

  checkFirstConfig();

  analogWrite(U_pwm, config.Ud);
  analogWrite(I_pwm, config.I);
}

/*
 *主循环
 */
void loop()
{
  doWiFiTick();      // 检查wifi
  chongDianQi();     // 运行充电器
  doTCPClientTick(); // tcp消息接收
}